package cn.sciento.starter.keyencrypt.core;

import cn.sciento.core.exception.CommonException;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.reflect.FieldUtils;
import cn.sciento.core.net.RequestHeaderCopyInterceptor;
import cn.sciento.starter.keyencrypt.json.CryptoModule;
import cn.sciento.starter.keyencrypt.mvc.EncryptPathVariableMethodArgumentResolver;
import cn.sciento.starter.keyencrypt.mvc.EncryptRequestParamMethodArgumentResolver;
import cn.sciento.starter.keyencrypt.mvc.EncryptRequestResponseBodyMethodProcessor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.PriorityOrdered;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;

public class WebBeanPostProcessor implements BeanPostProcessor, PriorityOrdered {
  protected ConfigurableBeanFactory beanFactory;
  
  protected IEncryptionService encryptionService;
  
  protected ObjectMapper objectMapper;
  
  public WebBeanPostProcessor(ConfigurableBeanFactory beanFactory, IEncryptionService encryptionService, ObjectMapper objectMapper) {
    this.beanFactory = beanFactory;
    this.encryptionService = encryptionService;
    this.objectMapper = objectMapper;
  }
  
  @Nullable
  public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    if (bean instanceof RestTemplate) {
      List<HttpMessageConverter<?>> messageConverters = ((RestTemplate)bean).getMessageConverters();
      if (!CollectionUtils.isEmpty(messageConverters)) {
        messageConverters.forEach(messageConverter -> {
              if (messageConverter instanceof MappingJackson2HttpMessageConverter) {
                ((MappingJackson2HttpMessageConverter) messageConverter).setObjectMapper(this.objectMapper);
              }
            });
      }
      List<ClientHttpRequestInterceptor> interceptors = ((RestTemplate)bean).getInterceptors();
      if (!CollectionUtils.isEmpty(interceptors)) {
        interceptors.forEach(interceptor -> {
              if (interceptor instanceof RequestHeaderCopyInterceptor) {
                ((RequestHeaderCopyInterceptor) interceptor).addIgnoreHeader("H-Request-Encrypt");
              }
            });
      }
    } 
    if (bean instanceof ObjectMapper) {
      ObjectMapper objectMapper = (ObjectMapper)bean;
      CryptoModule cryptoModule = new CryptoModule();
      this.encryptionService.setObjectMapper(objectMapper);
      cryptoModule.addEncryptionService(this.encryptionService);
      objectMapper.registerModule(cryptoModule);
      return objectMapper;
    } 
    if (bean instanceof RequestMappingHandlerAdapter) {
      EncryptRequestResponseBodyMethodProcessor encryptRequestResponseBodyMethodProcessor;
      RequestMappingHandlerAdapter adapter = (RequestMappingHandlerAdapter)bean;
      List<HandlerMethodArgumentResolver> currentResolvers = adapter.getArgumentResolvers();
      if (currentResolvers == null) {
        throw new IllegalStateException(
            String.format("No HandlerMethodArgumentResolvers found in RequestMappingHandlerAdapter %s!", beanName));
      }
      EncryptPathVariableMethodArgumentResolver encryptPathVariableMethodArgumentResolver = new EncryptPathVariableMethodArgumentResolver(this.encryptionService);
      EncryptRequestParamMethodArgumentResolver encryptRequestParamMethodArgumentResolver1 = new EncryptRequestParamMethodArgumentResolver(this.encryptionService, this.beanFactory, false);
      EncryptRequestParamMethodArgumentResolver encryptRequestParamMethodArgumentResolver2 = new EncryptRequestParamMethodArgumentResolver(this.encryptionService, this.beanFactory, true);
      try {
        encryptRequestResponseBodyMethodProcessor = new EncryptRequestResponseBodyMethodProcessor(adapter.getMessageConverters(), (List)FieldUtils.readDeclaredField(adapter, "requestResponseBodyAdvice", true));
      } catch (IllegalAccessException e) {
        throw new CommonException(e);
      } 
      encryptRequestResponseBodyMethodProcessor.setEncryptionService(this.encryptionService);
      List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(adapter.getArgumentResolvers().size());
      boolean isFirst = true;
      for (HandlerMethodArgumentResolver resolver : adapter.getArgumentResolvers()) {
        if (resolver instanceof org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver) {
          resolvers.add(encryptPathVariableMethodArgumentResolver);
          continue;
        } 
        if (resolver instanceof org.springframework.web.method.annotation.RequestParamMethodArgumentResolver) {
          if (isFirst) {
            resolvers.add(encryptRequestParamMethodArgumentResolver1);
            isFirst = false;
            continue;
          } 
          resolvers.add(encryptRequestParamMethodArgumentResolver2);
          continue;
        } 
        if (resolver instanceof org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor) {
          resolvers.add(encryptRequestResponseBodyMethodProcessor);
          continue;
        } 
        resolvers.add(resolver);
      } 
      adapter.setArgumentResolvers(resolvers);
      resolvers = new ArrayList<>(adapter.getInitBinderArgumentResolvers().size());
      isFirst = true;
      for (HandlerMethodArgumentResolver resolver : adapter.getInitBinderArgumentResolvers()) {
        if (resolver instanceof org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver) {
          resolvers.add(encryptPathVariableMethodArgumentResolver);
          continue;
        } 
        if (resolver instanceof org.springframework.web.method.annotation.RequestParamMethodArgumentResolver) {
          if (isFirst) {
            resolvers.add(encryptRequestParamMethodArgumentResolver1);
            isFirst = false;
            continue;
          } 
          resolvers.add(encryptRequestParamMethodArgumentResolver2);
          continue;
        } 
        resolvers.add(resolver);
      } 
      adapter.setInitBinderArgumentResolvers(resolvers);
      return adapter;
    } 
    return bean;
  }
  
  public int getOrder() {
    return Integer.MIN_VALUE;
  }
}
